package com.showsu.editor.session.redis;

import com.showsu.editor.utils.Constant;
import com.showsu.editor.utils.RedisUtil;
import com.showsu.editor.session.ISession;
import com.showsu.editor.session.ISessionManager;
import com.showsu.editor.session.SessionException;
import com.showsu.editor.utils.SessionIdGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @Author: William
 * @Description: redis会话管理器
 * @Date: Created in 11:02 上午 2024/9/20
 * @Modified By:
 */
@Component
@Slf4j
public class RedisSessionManager implements ISessionManager {

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 缓存key前缀
     */
    static final String CACHE_KEY_PREFIX = "showsu_editor:session:";

    /**
     * 默认过期时间为4H
     */
    static final long DEFAULT_EXP = 14400l;
    static final long DEFAULT_DAY_EXP = 7;

    /**
     * 会话的key生成策略
     * @param key
     * @return
     */
    private static String getSessionKey(String key) {
        return new StringBuilder(CACHE_KEY_PREFIX).append(key).toString();
    }

    /**
     * 会话队列的key生成策略
     * @param userId
     * @return
     */
    private static String getSessionQueueKey(String userId) {
        return new StringBuilder(CACHE_KEY_PREFIX).append("user:").append(userId).toString();
    }

    @Override
    public ISession getSession(HttpServletRequest request) throws SessionException {
        String sessionId = Constant.getSessionId(request);
        //没有获取到ck，证明客户从未登录过
        if (StringUtils.isBlank(sessionId)) {
            return null;
        }
        try {
            String key = getSessionKey(sessionId);
            String userId = redisUtil.getValue(key);
            //获取到ck，但是会话已失效
            if (StringUtils.isBlank(userId)) {
                return new RedisSession(sessionId, null);
            }
            //未过期的会话默认延长有效时间
            redisUtil.expireKey(key, DEFAULT_DAY_EXP, TimeUnit.DAYS);
            return new RedisSession(sessionId, Long.parseLong(userId));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new SessionException(e.getMessage());
        }
    }

    @Override
    public void deleteSession(String sessionId) throws SessionException {
        try {
            String key = getSessionKey(sessionId);
            String userId = redisUtil.getValue(key);
            if (StringUtils.isBlank(userId)) {
                return;
            }
            redisUtil.deleteKey(key);
            redisUtil.removeFromZSet(getSessionQueueKey(userId), sessionId);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new SessionException(e.getMessage());
        }
    }

    @Override
    public ISession createSession(Long userId) throws SessionException {
        try {
            ISession session = new RedisSession(SessionIdGenerator.generate(), userId);
            redisUtil.setValueWithExpire(getSessionKey(session.getSessionId()), userId.toString(), DEFAULT_DAY_EXP, TimeUnit.DAYS);
            // 目前一个账号仅允许一个设备登陆，后来者会把前人的会话顶掉。后续如果要支持多设备支持一套设备，再做改造。
            String sessionQueueKey = getSessionQueueKey(userId.toString());
            Set<String> existedSessions = redisUtil.getRangeByScore(sessionQueueKey, 1.0d, 1.0d);
            if (! CollectionUtils.isEmpty(existedSessions)) {
                for (String existedSession : existedSessions) {
                    redisUtil.deleteKey(getSessionKey(existedSession));
                    redisUtil.removeFromZSet(sessionQueueKey, existedSession);
                }
            }
            // 把本次生成的会话放入会话队列
            redisUtil.addToZSet(sessionQueueKey, session.getSessionId(), 1.0d);
            return session;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new SessionException(e.getMessage());
        }
    }
}
